package com.nodecollege.cloud.client.interceptor;

import com.alibaba.fastjson.JSONObject;
import com.nodecollege.cloud.client.feign.LoginClient;
import com.nodecollege.cloud.client.service.ApiService;
import com.nodecollege.cloud.common.UpmsConstants;
import com.nodecollege.cloud.common.annotation.ApiAnnotation;
import com.nodecollege.cloud.common.annotation.UserLoginAnnotation;
import com.nodecollege.cloud.common.aop.NCAop;
import com.nodecollege.cloud.common.constants.NCConstants;
import com.nodecollege.cloud.common.enums.ErrorEnum;
import com.nodecollege.cloud.common.exception.UpmsException;
import com.nodecollege.cloud.common.model.LoginUser;
import com.nodecollege.cloud.common.model.NCResult;
import com.nodecollege.cloud.common.model.po.UpmsApiVisit;
import com.nodecollege.cloud.common.model.vo.LoginVO;
import com.nodecollege.cloud.common.utils.NCUtils;
import com.nodecollege.cloud.common.utils.RedisUtils;
import com.nodecollege.cloud.common.utils.ThreadPoolFactory;
import com.nodecollege.cloud.common.utils.ThreadUserUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.ExecutorService;

/**
 * 全局登陆拦截器
 *
 * @author LC
 * @date 11:05 2020/1/10
 **/
public class GlobalLoginInterceptor extends HandlerInterceptorAdapter {

    private static final Logger logger = LoggerFactory.getLogger(GlobalLoginInterceptor.class);

    private static final ExecutorService GLOBAL_LOGIN_EXECUTOR = ThreadPoolFactory.getInstance();

    @Autowired
    private ApiService apiService;

    @Autowired
    private LoginClient loginClient;

    @Autowired
    private RedisUtils redisUtils;

    @Value("${spring.application.name:default}")
    private String applicationName;

    @Value("${upms.loginCheck:false}")
    private Boolean loginCheck;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 请求开始 清楚threadLocal中的数据
        ThreadUserUtils.resetLoginUser();
        String requestUrl = request.getRequestURI();
        String accessToken = request.getHeader(UpmsConstants.HEADER_ACCESS_TOKEN);
        String infoRandom = request.getHeader(UpmsConstants.HEADER_INFO_RANDOM);
        // 访问来源
        String accessSource = request.getHeader(UpmsConstants.HEADER_ACCESS_SOURCE);
        NCUtils.nullOrEmptyThrow(accessSource, new UpmsException("8888", "访问来源必填！"));
        String requestIp = NCUtils.getRequestIp(request);
        logger.info("访问地址：{}，访问token：{}, 访问来源：{}，访客ip：{}", requestUrl, accessToken, accessSource, requestIp);

        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            // 取method 有无用户登陆注解
            UserLoginAnnotation userLoginM = handlerMethod.getMethodAnnotation(UserLoginAnnotation.class);
            ApiAnnotation apiM = handlerMethod.getMethodAnnotation(ApiAnnotation.class);
            if (userLoginM == null) {
                userLoginM = AnnotationUtils.findAnnotation(handlerMethod.getBean().getClass(), UserLoginAnnotation.class);
            }

            UpmsApiVisit upmsApiVisit = new UpmsApiVisit();
            upmsApiVisit.setApplicationName(applicationName);
            upmsApiVisit.setControllerName(NCAop.getConciseMethodName(handlerMethod.getMethod().getDeclaringClass().getName()));
            upmsApiVisit.setApiUrl(requestUrl);
            upmsApiVisit.setRequestIp(requestIp);
            upmsApiVisit.setAccessSource(accessSource);
            if (apiM != null) {
                // 校验来源不为1时，需要强制校验来源信息
                if (NCConstants.INT_2.equals(apiM.accessSource())) {
                    if (accessSource.equals(UpmsConstants.ACCESS_SOURCE_NGINX)) {
                        throw new UpmsException("-1", "该接口只能内部调用！");
                    }
                    // 内部调用不需要校验登陆信息
                    GLOBAL_LOGIN_EXECUTOR.submit(() -> apiService.addApiVisitCache(upmsApiVisit));
                    return super.preHandle(request, response, handler);
                }
                if (NCConstants.INT_3.equals(apiM.accessSource()) && !accessSource.equals(UpmsConstants.ACCESS_SOURCE_NGINX)) {
                    throw new UpmsException("-1", "该接口只能通过nginx调用！");
                }
            }

            if (loginCheck && userLoginM != null) {
                LoginUser loginUser = userLoginInfoHandle(accessToken, infoRandom, applicationName + requestUrl, userLoginM.checkDataPower());
                upmsApiVisit.setTenantId(loginUser.getTenantId());
            }
            GLOBAL_LOGIN_EXECUTOR.submit(() -> apiService.addApiVisitCache(upmsApiVisit));
        }

        return super.preHandle(request, response, handler);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        super.afterCompletion(request, response, handler, ex);
        // 请求结束 清楚threadLocal中的数据 防止GC
        ThreadUserUtils.resetLoginUser();
    }

    /**
     * 添加当前登陆用户线程数据
     *
     * @param accessToken
     */
    private LoginUser userLoginInfoHandle(String accessToken, String infoRandom, String url, Boolean checkDataPower) {
        if (accessToken == null) {
            ErrorEnum.LOGIN_TIME_OUT.throwMsg();
        }
        String tokenKey = NCConstants.HEADER_ACCESS_TOKEN + applicationName + ":" + accessToken;
        LoginUser loginUser = redisUtils.get(tokenKey, LoginUser.class);
        if (NCUtils.isNullOrEmpty(loginUser) || !loginUser.getInfoRandom().equals(infoRandom)) {
            LoginVO loginVO = new LoginVO();
            loginVO.setToken(accessToken);
            NCResult<LoginUser> result = loginClient.getUserInfo(loginVO);
            if (result.getSuccess() && !result.getRows().isEmpty() && result.getRows().get(0) != null) {
                loginUser = result.getRows().get(0);
                redisUtils.set(tokenKey, loginUser, loginUser.getExpire());
            } else {
                ErrorEnum.LOGIN_TIME_OUT.throwMsg();
            }
        }
        // 是否校验接口数据权限
        if (checkDataPower) {
            loginUser.setApiDataPower(loginUser.getDataPower().getApiDataPower(url, loginUser.getShowAllRole()));
        }
        ThreadUserUtils.setLoginUser(loginUser);
        logger.debug("登陆用户信息： {}", JSONObject.toJSONString(ThreadUserUtils.getLoginUser()));
        return loginUser;
    }
}